﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Text;

namespace NetAcademia.Web.Mvc
{



    /// <summary>
    /// Extension methods
    /// </summary>
    public static class LaundryHelper
    {
        public static bool CaseInsensitiveEquals(this XName first, XName other)
        {
            if (first == null && other == null)
                return true;
            if (first == null || other == null)
                return false;

            return string.Compare(first.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase) == 0;
        }

        public static bool CaseInsensitiveNameEquals(this XAttribute first, XAttribute other)
        {
            if (first == null && other == null)
                return true;
            if (first == null || other == null)
                return false;

            return first.Name.CaseInsensitiveEquals(other.Name);
        }

        public static bool CaseInsensitiveNameEquals(this XElement first, XElement other)
        {
            if (first == null && other == null)
                return true;
            if (first == null || other == null)
                return false;

            return first.Name.CaseInsensitiveEquals(other.Name);
        }

        public static string CaseInsensitiveNameString(this XName name)
        {
            if (name == null)
                return null;
            return name.ToString().ToLowerInvariant();
        }

        public static string CaseInsensitiveNameString(this XAttribute attr)
        {
            if (attr == null)
                return null;

            return attr.Name.CaseInsensitiveNameString();
        }

        public static string CaseInsensitiveNameString(this XElement elem)
        {
            if (elem == null)
                return null;

            return elem.Name.CaseInsensitiveNameString();
        }

        /// <summary>
        /// Egy defaultWhitelist segítségével kigyomlálja az xml fát. Csak olyan elemek maradnak, ami a whitelistben megtalálható. (Composite)
        /// </summary>
        /// <param elementName="source">A kigyomlálandó dokumentum</param>
        /// <param elementName="defaultWhitelist">A megengedett elemek és attribútumokat tartalmazó doksi 
        /// <remarks>
        /// Nem számít, ha egymásba vannak ágyazva, csak egy név lehetőleg egyszer forduljon elő. 
        /// Nem mintha hibát okozna, de az attribútum lista ellenőrzéséhez az csak elsőt fogja használni.
        /// A gyökér elem nem lesz figyelembe véve.
        /// Az attribútumok üres értékkel azt jelentik, hogy az ilyen nevű attribútum maradhat. 
        /// Ha van érték megadva az attribútumnak, akkor azt egy regex ellenőrzésnek veszi, és csak akkor maradhat ez az attribútum a source ilyen nevű elemében ha a regexnek is megfelel.
        /// Kivétel a style attribútum, ahol a beágyazott stílusattribútumok pontosvesszővel vesszővel elválasztott listáját lehet validálásként megadni.
        /// </remarks>
        /// </param>
        public static void Clean(this XElement element, IEnumerable<XElement> whiteList)
        {
            var subelements = element.Elements().ToList(); //fixálom a bejárandó kollekciót

            foreach (var subelement in subelements) //összes gyerek elemmel ugyanezt végig, hogy leérjek a levelekig
            {
                subelement.Clean(whiteList);
            }

            //ElementComparer ec = new ElementComparer();
            XElement constraintElement = (from ce in whiteList
                                          where ce.CaseInsensitiveNameEquals(element)
                                          select ce).FirstOrDefault();

            //if (whiteList.Contains(element, ec)) //engedélyezett elem, ami maradhat
            if (constraintElement != null)
            {
                //de előtte az attribútunokat tisztogatni
                XElement attributePatterns = (from ce in whiteList
                                              where ce.Name.ToString().ToLower() == "attributes"
                                              select ce).FirstOrDefault();


                //default attribute patterns for emty patterns
                if (attributePatterns != null)
                {
                    var emptyAttributes = from attribA in constraintElement.Attributes()
                                          where attribA.Value == ""
                                          join attribB in attributePatterns.Attributes() on attribA.CaseInsensitiveNameString() equals attribB.CaseInsensitiveNameString() //attribA.Name equals attribB.Name
                                          select new { attribute = attribA, value = attribB.Value };

                    foreach (var attribute in emptyAttributes)
                    {
                        attribute.attribute.Value = attribute.value;
                    }
                }
                

                IEnumerable<XAttribute> myAttributes = element.Attributes().ToList(); //fixálom a bejárandó listát
                foreach (XAttribute attribute in myAttributes)
                {
                    XAttribute constarintAttribute = (from ca in constraintElement.Attributes()
                                                      where ca.CaseInsensitiveNameEquals(attribute)
                                                      select ca).FirstOrDefault();

                    if (constarintAttribute == null)
                    {
                        attribute.Remove();
                    }
                    else if (constarintAttribute.Value != string.Empty)
                    {
                        if (attribute.Name == "style") //ha style, akkor különleges bánásmód
                        {

                            //a style attribútumban definiált stílusok
                            Regex expAllow = new Regex(@"(?<name>\w+(-\w+)*\s*)(:\s*(?<value>(.*?)))?;");
                            if (!constarintAttribute.Value.EndsWith(";"))
                                constarintAttribute.Value += ";";

                            MatchCollection allowAttributes = expAllow.Matches(constarintAttribute.Value);

                            //a pontosvesszővel elválasztott stílusattribútumok megszerzése az source-ból
                            Regex exp = new Regex(@"(?<name>\w+(-\w+)*\s*):\s*(?<value>(.*?));");
                            //ehhez biztosiztosítom, hogy a végén is legyen pontosvessző
                            string currentAttributesString = attribute.Value.Trim();
                            if (!currentAttributesString.EndsWith(";"))
                                currentAttributesString += ";";

                            MatchCollection currentAttributes = exp.Matches(currentAttributesString);
                            StringBuilder cleanedAttributes = new StringBuilder();

                            Match[] allowArray = new Match[allowAttributes.Count];
                            allowAttributes.CopyTo(allowArray, 0);
                            Match[] currentArray = new Match[currentAttributes.Count];
                            currentAttributes.CopyTo(currentArray, 0);

                            var result = from matchAllow in allowArray
                                         join match in currentArray
                                         on matchAllow.Groups["name"].Value equals match.Groups["name"].Value
                                         select new
                                         {
                                             Name = matchAllow.Groups["name"].Value,
                                             expAllow = matchAllow.Groups["value"].Value,
                                             Value = match.Groups["value"].Value
                                         };

                            foreach (var item in result)
                            {
                                if (string.IsNullOrEmpty(item.expAllow))
                                {
                                    cleanedAttributes.AppendFormat("{0}:{1};", item.Name, item.Value);
                                }
                                else
                                {
                                    Regex expValue = new Regex(item.expAllow, RegexOptions.Singleline);
                                    Match valueMatch = expValue.Match(item.Value);
                                    if (valueMatch.Value != "")
                                    {
                                        cleanedAttributes.AppendFormat("{0}:{1};", item.Name, valueMatch.Value);
                                    }
                                }
                            }


                            if (cleanedAttributes.Length > 0)
                                attribute.Value = cleanedAttributes.ToString();
                            else //nem volt benne semmi megtartható, ezért dobjuk ki az egészet
                                attribute.Remove();


                        }
                        else //egyébként regex
                        {
                            string pattern = constarintAttribute.Value.Trim();
                            //if (!pattern.StartsWith("^"))
                                pattern = "^(" + pattern;
                            //if (!pattern.EndsWith("$"))
                                pattern += ")$";

                            Regex exp = new Regex(pattern );
                            var match = exp.Match(attribute.Value);
                            if (!match.Success)
                                attribute.Remove();
                        }
                    }

                }
            }
            else if (element.Parent != null) //ha a gyökérhez értünk akkor nincs hova tovább menekülni :)
            {
                var subnodes = element.Nodes().ToList(); //fixálom a bejárandó kollekciót

                foreach (var subnode in subnodes)
                {
                    subnode.Remove();
                    element.AddBeforeSelf(subnode); //kívülre másolok mindenkit
                }
                element.Remove();
            }


        }


        public static void Clean(this XDocument doc, IEnumerable<XElement> whiteList)
        {
            //doc.Descendants("script").Remove();
            //doc.Descendants("style").Remove();
            //doc.Descendants().Attributes().Where(a => a.Name.ToString().StartsWith("on")).Remove();

            if (whiteList != null)
                doc.Root.Clean(whiteList);

            //inkáb legyen sűrűbb, mint rengeteg felesleges enter
            doc.Descendants("p").Where(p => p.Nodes().Count() == 0).Remove();

        }
        //a whitelistben való kereséshez
        private class ElementComparer : IEqualityComparer<XElement>
        {
            #region IEqualityComparer<XElement> Members

            public bool Equals(XElement x, XElement y)
            {
                return x.Name == y.Name;
            }

            public int GetHashCode(XElement obj)
            {
                return obj.Name.GetHashCode();
            }

            #endregion
        }




    }
}

